/*
 * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */


package java.util.logging;

import java.lang.ref.WeakReference;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Supplier;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;

/**
 * A Logger object is used to log messages for a specific
 * system or application component.  Loggers are normally named,
 * using a hierarchical dot-separated namespace.  Logger names
 * can be arbitrary strings, but they should normally be based on
 * the package name or class name of the logged component, such
 * as java.net or javax.swing.  In addition it is possible to create
 * "anonymous" Loggers that are not stored in the Logger namespace.
 * <p>
 * Logger objects may be obtained by calls on one of the getLogger
 * factory methods.  These will either create a new Logger or
 * return a suitable existing Logger. It is important to note that
 * the Logger returned by one of the {@code getLogger} factory methods
 * may be garbage collected at any time if a strong reference to the
 * Logger is not kept.
 * <p>
 * Logging messages will be forwarded to registered Handler
 * objects, which can forward the messages to a variety of
 * destinations, including consoles, files, OS logs, etc.
 * <p>
 * Each Logger keeps track of a "parent" Logger, which is its
 * nearest existing ancestor in the Logger namespace.
 * <p>
 * Each Logger has a "Level" associated with it.  This reflects
 * a minimum Level that this logger cares about.  If a Logger's
 * level is set to <tt>null</tt>, then its effective level is inherited
 * from its parent, which may in turn obtain it recursively from its
 * parent, and so on up the tree.
 * <p>
 * The log level can be configured based on the properties from the
 * logging configuration file, as described in the description
 * of the LogManager class.  However it may also be dynamically changed
 * by calls on the Logger.setLevel method.  If a logger's level is
 * changed the change may also affect child loggers, since any child
 * logger that has <tt>null</tt> as its level will inherit its
 * effective level from its parent.
 * <p>
 * On each logging call the Logger initially performs a cheap
 * check of the request level (e.g., SEVERE or FINE) against the
 * effective log level of the logger.  If the request level is
 * lower than the log level, the logging call returns immediately.
 * <p>
 * After passing this initial (cheap) test, the Logger will allocate
 * a LogRecord to describe the logging message.  It will then call a
 * Filter (if present) to do a more detailed check on whether the
 * record should be published.  If that passes it will then publish
 * the LogRecord to its output Handlers.  By default, loggers also
 * publish to their parent's Handlers, recursively up the tree.
 * <p>
 * Each Logger may have a {@code ResourceBundle} associated with it.
 * The {@code ResourceBundle} may be specified by name, using the
 * {@link #getLogger(java.lang.String, java.lang.String)} factory
 * method, or by value - using the {@link
 * #setResourceBundle(java.util.ResourceBundle) setResourceBundle} method.
 * This bundle will be used for localizing logging messages.
 * If a Logger does not have its own {@code ResourceBundle} or resource bundle
 * name, then it will inherit the {@code ResourceBundle} or resource bundle name
 * from its parent, recursively up the tree.
 * <p>
 * Most of the logger output methods take a "msg" argument.  This
 * msg argument may be either a raw value or a localization key.
 * During formatting, if the logger has (or inherits) a localization
 * {@code ResourceBundle} and if the {@code ResourceBundle} has a mapping for
 * the msg string, then the msg string is replaced by the localized value.
 * Otherwise the original msg string is used.  Typically, formatters use
 * java.text.MessageFormat style formatting to format parameters, so
 * for example a format string "{0} {1}" would format two parameters
 * as strings.
 * <p>
 * A set of methods alternatively take a "msgSupplier" instead of a "msg"
 * argument.  These methods take a {@link Supplier}{@code <String>} function
 * which is invoked to construct the desired log message only when the message
 * actually is to be logged based on the effective log level thus eliminating
 * unnecessary message construction. For example, if the developer wants to
 * log system health status for diagnosis, with the String-accepting version,
 * the code would look like:
 * <pre><code>
 *
 * class DiagnosisMessages {
 * static String systemHealthStatus() {
 * // collect system health information
 * ...
 * }
 * }
 * ...
 * logger.log(Level.FINER, DiagnosisMessages.systemHealthStatus());
 * </code></pre>
 * With the above code, the health status is collected unnecessarily even when
 * the log level FINER is disabled. With the Supplier-accepting version as
 * below, the status will only be collected when the log level FINER is
 * enabled.
 * <pre><code>
 *
 * logger.log(Level.FINER, DiagnosisMessages::systemHealthStatus);
 * </code></pre>
 * <p>
 * When looking for a {@code ResourceBundle}, the logger will first look at
 * whether a bundle was specified using {@link
 * #setResourceBundle(java.util.ResourceBundle) setResourceBundle}, and then
 * only whether a resource bundle name was specified through the {@link
 * #getLogger(java.lang.String, java.lang.String) getLogger} factory method.
 * If no {@code ResourceBundle} or no resource bundle name is found,
 * then it will use the nearest {@code ResourceBundle} or resource bundle
 * name inherited from its parent tree.<br>
 * When a {@code ResourceBundle} was inherited or specified through the
 * {@link
 * #setResourceBundle(java.util.ResourceBundle) setResourceBundle} method, then
 * that {@code ResourceBundle} will be used. Otherwise if the logger only
 * has or inherited a resource bundle name, then that resource bundle name
 * will be mapped to a {@code ResourceBundle} object, using the default Locale
 * at the time of logging.
 * <br id="ResourceBundleMapping">When mapping resource bundle names to
 * {@code ResourceBundle} objects, the logger will first try to use the
 * Thread's {@linkplain java.lang.Thread#getContextClassLoader() context class
 * loader} to map the given resource bundle name to a {@code ResourceBundle}.
 * If the thread context class loader is {@code null}, it will try the
 * {@linkplain java.lang.ClassLoader#getSystemClassLoader() system class loader}
 * instead.  If the {@code ResourceBundle} is still not found, it will use the
 * class loader of the first caller of the {@link
 * #getLogger(java.lang.String, java.lang.String) getLogger} factory method.
 * <p>
 * Formatting (including localization) is the responsibility of
 * the output Handler, which will typically call a Formatter.
 * <p>
 * Note that formatting need not occur synchronously.  It may be delayed
 * until a LogRecord is actually written to an external sink.
 * <p>
 * The logging methods are grouped in five main categories:
 * <ul>
 * <li><p>
 * There are a set of "log" methods that take a log level, a message
 * string, and optionally some parameters to the message string.
 * <li><p>
 * There are a set of "logp" methods (for "log precise") that are
 * like the "log" methods, but also take an explicit source class name
 * and method name.
 * <li><p>
 * There are a set of "logrb" method (for "log with resource bundle")
 * that are like the "logp" method, but also take an explicit resource
 * bundle object for use in localizing the log message.
 * <li><p>
 * There are convenience methods for tracing method entries (the
 * "entering" methods), method returns (the "exiting" methods) and
 * throwing exceptions (the "throwing" methods).
 * <li><p>
 * Finally, there are a set of convenience methods for use in the
 * very simplest cases, when a developer simply wants to log a
 * simple string at a given log level.  These methods are named
 * after the standard Level names ("severe", "warning", "info", etc.)
 * and take a single argument, a message string.
 * </ul>
 * <p>
 * For the methods that do not take an explicit source name and
 * method name, the Logging framework will make a "best effort"
 * to determine which class and method called into the logging method.
 * However, it is important to realize that this automatically inferred
 * information may only be approximate (or may even be quite wrong!).
 * Virtual machines are allowed to do extensive optimizations when
 * JITing and may entirely remove stack frames, making it impossible
 * to reliably locate the calling class and method.
 * <P>
 * All methods on Logger are multi-thread safe.
 * <p>
 * <b>Subclassing Information:</b> Note that a LogManager class may
 * provide its own implementation of named Loggers for any point in
 * the namespace.  Therefore, any subclasses of Logger (unless they
 * are implemented in conjunction with a new LogManager class) should
 * take care to obtain a Logger instance from the LogManager class and
 * should delegate operations such as "isLoggable" and "log(LogRecord)"
 * to that instance.  Note that in order to intercept all logging
 * output, subclasses need only override the log(LogRecord) method.
 * All the other logging methods are implemented as calls on this
 * log(LogRecord) method.
 *
 * @since 1.4
 */
public class Logger {

  private static final Handler emptyHandlers[] = new Handler[0];
  private static final int offValue = Level.OFF.intValue();

  static final String SYSTEM_LOGGER_RB_NAME = "sun.util.logging.resources.logging";

  // This class is immutable and it is important that it remains so.
  private static final class LoggerBundle {

    final String resourceBundleName; // Base name of the bundle.
    final ResourceBundle userBundle; // Bundle set through setResourceBundle.

    private LoggerBundle(String resourceBundleName, ResourceBundle bundle) {
      this.resourceBundleName = resourceBundleName;
      this.userBundle = bundle;
    }

    boolean isSystemBundle() {
      return SYSTEM_LOGGER_RB_NAME.equals(resourceBundleName);
    }

    static LoggerBundle get(String name, ResourceBundle bundle) {
      if (name == null && bundle == null) {
        return NO_RESOURCE_BUNDLE;
      } else if (SYSTEM_LOGGER_RB_NAME.equals(name) && bundle == null) {
        return SYSTEM_BUNDLE;
      } else {
        return new LoggerBundle(name, bundle);
      }
    }
  }

  // This instance will be shared by all loggers created by the system
  // code
  private static final LoggerBundle SYSTEM_BUNDLE =
      new LoggerBundle(SYSTEM_LOGGER_RB_NAME, null);

  // This instance indicates that no resource bundle has been specified yet,
  // and it will be shared by all loggers which have no resource bundle.
  private static final LoggerBundle NO_RESOURCE_BUNDLE =
      new LoggerBundle(null, null);

  private volatile LogManager manager;
  private String name;
  private final CopyOnWriteArrayList<Handler> handlers =
      new CopyOnWriteArrayList<>();
  private volatile LoggerBundle loggerBundle = NO_RESOURCE_BUNDLE;
  private volatile boolean useParentHandlers = true;
  private volatile Filter filter;
  private boolean anonymous;

  // Cache to speed up behavior of findResourceBundle:
  private ResourceBundle catalog;     // Cached resource bundle
  private String catalogName;         // name associated with catalog
  private Locale catalogLocale;       // locale associated with catalog

  // The fields relating to parent-child relationships and levels
  // are managed under a separate lock, the treeLock.
  private static final Object treeLock = new Object();
  // We keep weak references from parents to children, but strong
  // references from children to parents.
  private volatile Logger parent;    // our nearest parent.
  private ArrayList<LogManager.LoggerWeakRef> kids;   // WeakReferences to loggers that have us as parent
  private volatile Level levelObject;
  private volatile int levelValue;  // current effective level value
  private WeakReference<ClassLoader> callersClassLoaderRef;
  private final boolean isSystemLogger;

  /**
   * GLOBAL_LOGGER_NAME is a name for the global logger.
   *
   * @since 1.6
   */
  public static final String GLOBAL_LOGGER_NAME = "global";

  /**
   * Return global logger object with the name Logger.GLOBAL_LOGGER_NAME.
   *
   * @return global logger object
   * @since 1.7
   */
  public static final Logger getGlobal() {
    // In order to break a cyclic dependence between the LogManager
    // and Logger static initializers causing deadlocks, the global
    // logger is created with a special constructor that does not
    // initialize its log manager.
    //
    // If an application calls Logger.getGlobal() before any logger
    // has been initialized, it is therefore possible that the
    // LogManager class has not been initialized yet, and therefore
    // Logger.global.manager will be null.
    //
    // In order to finish the initialization of the global logger, we
    // will therefore call LogManager.getLogManager() here.
    //
    // To prevent race conditions we also need to call
    // LogManager.getLogManager() unconditionally here.
    // Indeed we cannot rely on the observed value of global.manager,
    // because global.manager will become not null somewhere during
    // the initialization of LogManager.
    // If two threads are calling getGlobal() concurrently, one thread
    // will see global.manager null and call LogManager.getLogManager(),
    // but the other thread could come in at a time when global.manager
    // is already set although ensureLogManagerInitialized is not finished
    // yet...
    // Calling LogManager.getLogManager() unconditionally will fix that.

    LogManager.getLogManager();

    // Now the global LogManager should be initialized,
    // and the global logger should have been added to
    // it, unless we were called within the constructor of a LogManager
    // subclass installed as LogManager, in which case global.manager
    // would still be null, and global will be lazily initialized later on.

    return global;
  }

  /**
   * The "global" Logger object is provided as a convenience to developers
   * who are making casual use of the Logging package.  Developers
   * who are making serious use of the logging package (for example
   * in products) should create and use their own Logger objects,
   * with appropriate names, so that logging can be controlled on a
   * suitable per-Logger granularity. Developers also need to keep a
   * strong reference to their Logger objects to prevent them from
   * being garbage collected.
   * <p>
   *
   * @deprecated Initialization of this field is prone to deadlocks. The field must be initialized
   * by the Logger class initialization which may cause deadlocks with the LogManager class
   * initialization. In such cases two class initialization wait for each other to complete. The
   * preferred way to get the global logger object is via the call <code>Logger.getGlobal()</code>.
   * For compatibility with old JDK versions where the <code>Logger.getGlobal()</code> is not
   * available use the call <code>Logger.getLogger(Logger.GLOBAL_LOGGER_NAME)</code> or
   * <code>Logger.getLogger("global")</code>.
   */
  @Deprecated
  public static final Logger global = new Logger(GLOBAL_LOGGER_NAME);

  /**
   * Protected method to construct a logger for a named subsystem.
   * <p>
   * The logger will be initially configured with a null Level
   * and with useParentHandlers set to true.
   *
   * @param name A name for the logger.  This should be a dot-separated name and should normally be
   * based on the package name or class name of the subsystem, such as java.net or javax.swing.  It
   * may be null for anonymous Loggers.
   * @param resourceBundleName name of ResourceBundle to be used for localizing messages for this
   * logger.  May be null if none of the messages require localization.
   * @throws MissingResourceException if the resourceBundleName is non-null and no corresponding
   * resource can be found.
   */
  protected Logger(String name, String resourceBundleName) {
    this(name, resourceBundleName, null, LogManager.getLogManager(), false);
  }

  Logger(String name, String resourceBundleName, Class<?> caller, LogManager manager,
      boolean isSystemLogger) {
    this.manager = manager;
    this.isSystemLogger = isSystemLogger;
    setupResourceInfo(resourceBundleName, caller);
    this.name = name;
    levelValue = Level.INFO.intValue();
  }

  private void setCallersClassLoaderRef(Class<?> caller) {
    ClassLoader callersClassLoader = ((caller != null)
        ? caller.getClassLoader()
        : null);
    if (callersClassLoader != null) {
      this.callersClassLoaderRef = new WeakReference<>(callersClassLoader);
    }
  }

  private ClassLoader getCallersClassLoader() {
    return (callersClassLoaderRef != null)
        ? callersClassLoaderRef.get()
        : null;
  }

  // This constructor is used only to create the global Logger.
  // It is needed to break a cyclic dependence between the LogManager
  // and Logger static initializers causing deadlocks.
  private Logger(String name) {
    // The manager field is not initialized here.
    this.name = name;
    this.isSystemLogger = true;
    levelValue = Level.INFO.intValue();
  }

  // It is called from LoggerContext.addLocalLogger() when the logger
  // is actually added to a LogManager.
  void setLogManager(LogManager manager) {
    this.manager = manager;
  }

  private void checkPermission() throws SecurityException {
    if (!anonymous) {
      if (manager == null) {
        // Complete initialization of the global Logger.
        manager = LogManager.getLogManager();
      }
      manager.checkPermission();
    }
  }

  // Until all JDK code converted to call sun.util.logging.PlatformLogger
  // (see 7054233), we need to determine if Logger.getLogger is to add
  // a system logger or user logger.
  //
  // As an interim solution, if the immediate caller whose caller loader is
  // null, we assume it's a system logger and add it to the system context.
  // These system loggers only set the resource bundle to the given
  // resource bundle name (rather than the default system resource bundle).
  private static class SystemLoggerHelper {

    static boolean disableCallerCheck = getBooleanProperty("sun.util.logging.disableCallerCheck");

    private static boolean getBooleanProperty(final String key) {
      String s = AccessController.doPrivileged(new PrivilegedAction<String>() {
        @Override
        public String run() {
          return System.getProperty(key);
        }
      });
      return Boolean.valueOf(s);
    }
  }

  private static Logger demandLogger(String name, String resourceBundleName, Class<?> caller) {
    LogManager manager = LogManager.getLogManager();
    SecurityManager sm = System.getSecurityManager();
    if (sm != null && !SystemLoggerHelper.disableCallerCheck) {
      if (caller.getClassLoader() == null) {
        return manager.demandSystemLogger(name, resourceBundleName);
      }
    }
    return manager.demandLogger(name, resourceBundleName, caller);
    // ends up calling new Logger(name, resourceBundleName, caller)
    // iff the logger doesn't exist already
  }

  /**
   * Find or create a logger for a named subsystem.  If a logger has
   * already been created with the given name it is returned.  Otherwise
   * a new logger is created.
   * <p>
   * If a new logger is created its log level will be configured
   * based on the LogManager configuration and it will configured
   * to also send logging output to its parent's Handlers.  It will
   * be registered in the LogManager global namespace.
   * <p>
   * Note: The LogManager may only retain a weak reference to the newly
   * created Logger. It is important to understand that a previously
   * created Logger with the given name may be garbage collected at any
   * time if there is no strong reference to the Logger. In particular,
   * this means that two back-to-back calls like
   * {@code getLogger("MyLogger").log(...)} may use different Logger
   * objects named "MyLogger" if there is no strong reference to the
   * Logger named "MyLogger" elsewhere in the program.
   *
   * @param name A name for the logger.  This should be a dot-separated name and should normally be
   * based on the package name or class name of the subsystem, such as java.net or javax.swing
   * @return a suitable Logger
   * @throws NullPointerException if the name is null.
   */

  // Synchronization is not required here. All synchronization for
  // adding a new Logger object is handled by LogManager.addLogger().
  @CallerSensitive
  public static Logger getLogger(String name) {
    // This method is intentionally not a wrapper around a call
    // to getLogger(name, resourceBundleName). If it were then
    // this sequence:
    //
    //     getLogger("Foo", "resourceBundleForFoo");
    //     getLogger("Foo");
    //
    // would throw an IllegalArgumentException in the second call
    // because the wrapper would result in an attempt to replace
    // the existing "resourceBundleForFoo" with null.
    return demandLogger(name, null, Reflection.getCallerClass());
  }

  /**
   * Find or create a logger for a named subsystem.  If a logger has
   * already been created with the given name it is returned.  Otherwise
   * a new logger is created.
   * <p>
   * If a new logger is created its log level will be configured
   * based on the LogManager and it will configured to also send logging
   * output to its parent's Handlers.  It will be registered in
   * the LogManager global namespace.
   * <p>
   * Note: The LogManager may only retain a weak reference to the newly
   * created Logger. It is important to understand that a previously
   * created Logger with the given name may be garbage collected at any
   * time if there is no strong reference to the Logger. In particular,
   * this means that two back-to-back calls like
   * {@code getLogger("MyLogger", ...).log(...)} may use different Logger
   * objects named "MyLogger" if there is no strong reference to the
   * Logger named "MyLogger" elsewhere in the program.
   * <p>
   * If the named Logger already exists and does not yet have a
   * localization resource bundle then the given resource bundle
   * name is used.  If the named Logger already exists and has
   * a different resource bundle name then an IllegalArgumentException
   * is thrown.
   * <p>
   *
   * @param name A name for the logger.  This should be a dot-separated name and should normally be
   * based on the package name or class name of the subsystem, such as java.net or javax.swing
   * @param resourceBundleName name of ResourceBundle to be used for localizing messages for this
   * logger. May be {@code null} if none of the messages require localization.
   * @return a suitable Logger
   * @throws MissingResourceException if the resourceBundleName is non-null and no corresponding
   * resource can be found.
   * @throws IllegalArgumentException if the Logger already exists and uses a different resource
   * bundle name; or if {@code resourceBundleName} is {@code null} but the named logger has a
   * resource bundle set.
   * @throws NullPointerException if the name is null.
   */

  // Synchronization is not required here. All synchronization for
  // adding a new Logger object is handled by LogManager.addLogger().
  @CallerSensitive
  public static Logger getLogger(String name, String resourceBundleName) {
    Class<?> callerClass = Reflection.getCallerClass();
    Logger result = demandLogger(name, resourceBundleName, callerClass);

    // MissingResourceException or IllegalArgumentException can be
    // thrown by setupResourceInfo().
    // We have to set the callers ClassLoader here in case demandLogger
    // above found a previously created Logger.  This can happen, for
    // example, if Logger.getLogger(name) is called and subsequently
    // Logger.getLogger(name, resourceBundleName) is called.  In this case
    // we won't necessarily have the correct classloader saved away, so
    // we need to set it here, too.

    result.setupResourceInfo(resourceBundleName, callerClass);
    return result;
  }

  // package-private
  // Add a platform logger to the system context.
  // i.e. caller of sun.util.logging.PlatformLogger.getLogger
  static Logger getPlatformLogger(String name) {
    LogManager manager = LogManager.getLogManager();

    // all loggers in the system context will default to
    // the system logger's resource bundle
    Logger result = manager.demandSystemLogger(name, SYSTEM_LOGGER_RB_NAME);
    return result;
  }

  /**
   * Create an anonymous Logger.  The newly created Logger is not
   * registered in the LogManager namespace.  There will be no
   * access checks on updates to the logger.
   * <p>
   * This factory method is primarily intended for use from applets.
   * Because the resulting Logger is anonymous it can be kept private
   * by the creating class.  This removes the need for normal security
   * checks, which in turn allows untrusted applet code to update
   * the control state of the Logger.  For example an applet can do
   * a setLevel or an addHandler on an anonymous Logger.
   * <p>
   * Even although the new logger is anonymous, it is configured
   * to have the root logger ("") as its parent.  This means that
   * by default it inherits its effective level and handlers
   * from the root logger. Changing its parent via the
   * {@link #setParent(java.util.logging.Logger) setParent} method
   * will still require the security permission specified by that method.
   * <p>
   *
   * @return a newly created private Logger
   */
  public static Logger getAnonymousLogger() {
    return getAnonymousLogger(null);
  }

  /**
   * Create an anonymous Logger.  The newly created Logger is not
   * registered in the LogManager namespace.  There will be no
   * access checks on updates to the logger.
   * <p>
   * This factory method is primarily intended for use from applets.
   * Because the resulting Logger is anonymous it can be kept private
   * by the creating class.  This removes the need for normal security
   * checks, which in turn allows untrusted applet code to update
   * the control state of the Logger.  For example an applet can do
   * a setLevel or an addHandler on an anonymous Logger.
   * <p>
   * Even although the new logger is anonymous, it is configured
   * to have the root logger ("") as its parent.  This means that
   * by default it inherits its effective level and handlers
   * from the root logger.  Changing its parent via the
   * {@link #setParent(java.util.logging.Logger) setParent} method
   * will still require the security permission specified by that method.
   * <p>
   *
   * @param resourceBundleName name of ResourceBundle to be used for localizing messages for this
   * logger. May be null if none of the messages require localization.
   * @return a newly created private Logger
   * @throws MissingResourceException if the resourceBundleName is non-null and no corresponding
   * resource can be found.
   */

  // Synchronization is not required here. All synchronization for
  // adding a new anonymous Logger object is handled by doSetParent().
  @CallerSensitive
  public static Logger getAnonymousLogger(String resourceBundleName) {
    LogManager manager = LogManager.getLogManager();
    // cleanup some Loggers that have been GC'ed
    manager.drainLoggerRefQueueBounded();
    Logger result = new Logger(null, resourceBundleName,
        Reflection.getCallerClass(), manager, false);
    result.anonymous = true;
    Logger root = manager.getLogger("");
    result.doSetParent(root);
    return result;
  }

  /**
   * Retrieve the localization resource bundle for this
   * logger.
   * This method will return a {@code ResourceBundle} that was either
   * set by the {@link
   * #setResourceBundle(java.util.ResourceBundle) setResourceBundle} method or
   * <a href="#ResourceBundleMapping">mapped from the
   * the resource bundle name</a> set via the {@link
   * Logger#getLogger(java.lang.String, java.lang.String) getLogger} factory
   * method for the current default locale.
   * <br>Note that if the result is {@code null}, then the Logger will use a resource
   * bundle or resource bundle name inherited from its parent.
   *
   * @return localization bundle (may be {@code null})
   */
  public ResourceBundle getResourceBundle() {
    return findResourceBundle(getResourceBundleName(), true);
  }

  /**
   * Retrieve the localization resource bundle name for this
   * logger.
   * This is either the name specified through the {@link
   * #getLogger(java.lang.String, java.lang.String) getLogger} factory method,
   * or the {@linkplain ResourceBundle#getBaseBundleName() base name} of the
   * ResourceBundle set through {@link
   * #setResourceBundle(java.util.ResourceBundle) setResourceBundle} method.
   * <br>Note that if the result is {@code null}, then the Logger will use a resource
   * bundle or resource bundle name inherited from its parent.
   *
   * @return localization bundle name (may be {@code null})
   */
  public String getResourceBundleName() {
    return loggerBundle.resourceBundleName;
  }

  /**
   * Set a filter to control output on this Logger.
   * <P>
   * After passing the initial "level" check, the Logger will
   * call this Filter to check if a log record should really
   * be published.
   *
   * @param newFilter a filter object (may be null)
   * @throws SecurityException if a security manager exists, this logger is not anonymous, and the
   * caller does not have LoggingPermission("control").
   */
  public void setFilter(Filter newFilter) throws SecurityException {
    checkPermission();
    filter = newFilter;
  }

  /**
   * Get the current filter for this Logger.
   *
   * @return a filter object (may be null)
   */
  public Filter getFilter() {
    return filter;
  }

  /**
   * Log a LogRecord.
   * <p>
   * All the other logging methods in this class call through
   * this method to actually perform any logging.  Subclasses can
   * override this single method to capture all log activity.
   *
   * @param record the LogRecord to be published
   */
  public void log(LogRecord record) {
    if (!isLoggable(record.getLevel())) {
      return;
    }
    Filter theFilter = filter;
    if (theFilter != null && !theFilter.isLoggable(record)) {
      return;
    }

    // Post the LogRecord to all our Handlers, and then to
    // our parents' handlers, all the way up the tree.

    Logger logger = this;
    while (logger != null) {
      final Handler[] loggerHandlers = isSystemLogger
          ? logger.accessCheckedHandlers()
          : logger.getHandlers();

      for (Handler handler : loggerHandlers) {
        handler.publish(record);
      }

      final boolean useParentHdls = isSystemLogger
          ? logger.useParentHandlers
          : logger.getUseParentHandlers();

      if (!useParentHdls) {
        break;
      }

      logger = isSystemLogger ? logger.parent : logger.getParent();
    }
  }

  // private support method for logging.
  // We fill in the logger name, resource bundle name, and
  // resource bundle and then call "void log(LogRecord)".
  private void doLog(LogRecord lr) {
    lr.setLoggerName(name);
    final LoggerBundle lb = getEffectiveLoggerBundle();
    final ResourceBundle bundle = lb.userBundle;
    final String ebname = lb.resourceBundleName;
    if (ebname != null && bundle != null) {
      lr.setResourceBundleName(ebname);
      lr.setResourceBundle(bundle);
    }
    log(lr);
  }

  //================================================================
  // Start of convenience methods WITHOUT className and methodName
  //================================================================

  /**
   * Log a message, with no arguments.
   * <p>
   * If the logger is currently enabled for the given message
   * level then the given message is forwarded to all the
   * registered output Handler objects.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param msg The string message (or a key in the message catalog)
   */
  public void log(Level level, String msg) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msg);
    doLog(lr);
  }

  /**
   * Log a message, which is only to be constructed if the logging level
   * is such that the message will actually be logged.
   * <p>
   * If the logger is currently enabled for the given message
   * level then the message is constructed by invoking the provided
   * supplier function and forwarded to all the registered output
   * Handler objects.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param msgSupplier A function, which when called, produces the desired log message
   */
  public void log(Level level, Supplier<String> msgSupplier) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msgSupplier.get());
    doLog(lr);
  }

  /**
   * Log a message, with one object parameter.
   * <p>
   * If the logger is currently enabled for the given message
   * level then a corresponding LogRecord is created and forwarded
   * to all the registered output Handler objects.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param msg The string message (or a key in the message catalog)
   * @param param1 parameter to the message
   */
  public void log(Level level, String msg, Object param1) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msg);
    Object params[] = {param1};
    lr.setParameters(params);
    doLog(lr);
  }

  /**
   * Log a message, with an array of object arguments.
   * <p>
   * If the logger is currently enabled for the given message
   * level then a corresponding LogRecord is created and forwarded
   * to all the registered output Handler objects.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param msg The string message (or a key in the message catalog)
   * @param params array of parameters to the message
   */
  public void log(Level level, String msg, Object params[]) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msg);
    lr.setParameters(params);
    doLog(lr);
  }

  /**
   * Log a message, with associated Throwable information.
   * <p>
   * If the logger is currently enabled for the given message
   * level then the given arguments are stored in a LogRecord
   * which is forwarded to all registered output handlers.
   * <p>
   * Note that the thrown argument is stored in the LogRecord thrown
   * property, rather than the LogRecord parameters property.  Thus it is
   * processed specially by output Formatters and is not treated
   * as a formatting parameter to the LogRecord message property.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param msg The string message (or a key in the message catalog)
   * @param thrown Throwable associated with log message.
   */
  public void log(Level level, String msg, Throwable thrown) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msg);
    lr.setThrown(thrown);
    doLog(lr);
  }

  /**
   * Log a lazily constructed message, with associated Throwable information.
   * <p>
   * If the logger is currently enabled for the given message level then the
   * message is constructed by invoking the provided supplier function. The
   * message and the given {@link Throwable} are then stored in a {@link
   * LogRecord} which is forwarded to all registered output handlers.
   * <p>
   * Note that the thrown argument is stored in the LogRecord thrown
   * property, rather than the LogRecord parameters property.  Thus it is
   * processed specially by output Formatters and is not treated
   * as a formatting parameter to the LogRecord message property.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param thrown Throwable associated with log message.
   * @param msgSupplier A function, which when called, produces the desired log message
   * @since 1.8
   */
  public void log(Level level, Throwable thrown, Supplier<String> msgSupplier) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msgSupplier.get());
    lr.setThrown(thrown);
    doLog(lr);
  }

  //================================================================
  // Start of convenience methods WITH className and methodName
  //================================================================

  /**
   * Log a message, specifying source class and method,
   * with no arguments.
   * <p>
   * If the logger is currently enabled for the given message
   * level then the given message is forwarded to all the
   * registered output Handler objects.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param sourceClass name of class that issued the logging request
   * @param sourceMethod name of method that issued the logging request
   * @param msg The string message (or a key in the message catalog)
   */
  public void logp(Level level, String sourceClass, String sourceMethod, String msg) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msg);
    lr.setSourceClassName(sourceClass);
    lr.setSourceMethodName(sourceMethod);
    doLog(lr);
  }

  /**
   * Log a lazily constructed message, specifying source class and method,
   * with no arguments.
   * <p>
   * If the logger is currently enabled for the given message
   * level then the message is constructed by invoking the provided
   * supplier function and forwarded to all the registered output
   * Handler objects.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param sourceClass name of class that issued the logging request
   * @param sourceMethod name of method that issued the logging request
   * @param msgSupplier A function, which when called, produces the desired log message
   * @since 1.8
   */
  public void logp(Level level, String sourceClass, String sourceMethod,
      Supplier<String> msgSupplier) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msgSupplier.get());
    lr.setSourceClassName(sourceClass);
    lr.setSourceMethodName(sourceMethod);
    doLog(lr);
  }

  /**
   * Log a message, specifying source class and method,
   * with a single object parameter to the log message.
   * <p>
   * If the logger is currently enabled for the given message
   * level then a corresponding LogRecord is created and forwarded
   * to all the registered output Handler objects.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param sourceClass name of class that issued the logging request
   * @param sourceMethod name of method that issued the logging request
   * @param msg The string message (or a key in the message catalog)
   * @param param1 Parameter to the log message.
   */
  public void logp(Level level, String sourceClass, String sourceMethod,
      String msg, Object param1) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msg);
    lr.setSourceClassName(sourceClass);
    lr.setSourceMethodName(sourceMethod);
    Object params[] = {param1};
    lr.setParameters(params);
    doLog(lr);
  }

  /**
   * Log a message, specifying source class and method,
   * with an array of object arguments.
   * <p>
   * If the logger is currently enabled for the given message
   * level then a corresponding LogRecord is created and forwarded
   * to all the registered output Handler objects.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param sourceClass name of class that issued the logging request
   * @param sourceMethod name of method that issued the logging request
   * @param msg The string message (or a key in the message catalog)
   * @param params Array of parameters to the message
   */
  public void logp(Level level, String sourceClass, String sourceMethod,
      String msg, Object params[]) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msg);
    lr.setSourceClassName(sourceClass);
    lr.setSourceMethodName(sourceMethod);
    lr.setParameters(params);
    doLog(lr);
  }

  /**
   * Log a message, specifying source class and method,
   * with associated Throwable information.
   * <p>
   * If the logger is currently enabled for the given message
   * level then the given arguments are stored in a LogRecord
   * which is forwarded to all registered output handlers.
   * <p>
   * Note that the thrown argument is stored in the LogRecord thrown
   * property, rather than the LogRecord parameters property.  Thus it is
   * processed specially by output Formatters and is not treated
   * as a formatting parameter to the LogRecord message property.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param sourceClass name of class that issued the logging request
   * @param sourceMethod name of method that issued the logging request
   * @param msg The string message (or a key in the message catalog)
   * @param thrown Throwable associated with log message.
   */
  public void logp(Level level, String sourceClass, String sourceMethod,
      String msg, Throwable thrown) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msg);
    lr.setSourceClassName(sourceClass);
    lr.setSourceMethodName(sourceMethod);
    lr.setThrown(thrown);
    doLog(lr);
  }

  /**
   * Log a lazily constructed message, specifying source class and method,
   * with associated Throwable information.
   * <p>
   * If the logger is currently enabled for the given message level then the
   * message is constructed by invoking the provided supplier function. The
   * message and the given {@link Throwable} are then stored in a {@link
   * LogRecord} which is forwarded to all registered output handlers.
   * <p>
   * Note that the thrown argument is stored in the LogRecord thrown
   * property, rather than the LogRecord parameters property.  Thus it is
   * processed specially by output Formatters and is not treated
   * as a formatting parameter to the LogRecord message property.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param sourceClass name of class that issued the logging request
   * @param sourceMethod name of method that issued the logging request
   * @param thrown Throwable associated with log message.
   * @param msgSupplier A function, which when called, produces the desired log message
   * @since 1.8
   */
  public void logp(Level level, String sourceClass, String sourceMethod,
      Throwable thrown, Supplier<String> msgSupplier) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msgSupplier.get());
    lr.setSourceClassName(sourceClass);
    lr.setSourceMethodName(sourceMethod);
    lr.setThrown(thrown);
    doLog(lr);
  }

  //=========================================================================
  // Start of convenience methods WITH className, methodName and bundle name.
  //=========================================================================

  // Private support method for logging for "logrb" methods.
  // We fill in the logger name, resource bundle name, and
  // resource bundle and then call "void log(LogRecord)".
  private void doLog(LogRecord lr, String rbname) {
    lr.setLoggerName(name);
    if (rbname != null) {
      lr.setResourceBundleName(rbname);
      lr.setResourceBundle(findResourceBundle(rbname, false));
    }
    log(lr);
  }

  // Private support method for logging for "logrb" methods.
  private void doLog(LogRecord lr, ResourceBundle rb) {
    lr.setLoggerName(name);
    if (rb != null) {
      lr.setResourceBundleName(rb.getBaseBundleName());
      lr.setResourceBundle(rb);
    }
    log(lr);
  }

  /**
   * Log a message, specifying source class, method, and resource bundle name
   * with no arguments.
   * <p>
   * If the logger is currently enabled for the given message
   * level then the given message is forwarded to all the
   * registered output Handler objects.
   * <p>
   * The msg string is localized using the named resource bundle.  If the
   * resource bundle name is null, or an empty String or invalid
   * then the msg string is not localized.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param sourceClass name of class that issued the logging request
   * @param sourceMethod name of method that issued the logging request
   * @param bundleName name of resource bundle to localize msg, can be null
   * @param msg The string message (or a key in the message catalog)
   * @deprecated Use {@link #logrb(java.util.logging.Level, java.lang.String, java.lang.String,
   * java.util.ResourceBundle, java.lang.String, java.lang.Object...)} instead.
   */
  @Deprecated
  public void logrb(Level level, String sourceClass, String sourceMethod,
      String bundleName, String msg) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msg);
    lr.setSourceClassName(sourceClass);
    lr.setSourceMethodName(sourceMethod);
    doLog(lr, bundleName);
  }

  /**
   * Log a message, specifying source class, method, and resource bundle name,
   * with a single object parameter to the log message.
   * <p>
   * If the logger is currently enabled for the given message
   * level then a corresponding LogRecord is created and forwarded
   * to all the registered output Handler objects.
   * <p>
   * The msg string is localized using the named resource bundle.  If the
   * resource bundle name is null, or an empty String or invalid
   * then the msg string is not localized.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param sourceClass name of class that issued the logging request
   * @param sourceMethod name of method that issued the logging request
   * @param bundleName name of resource bundle to localize msg, can be null
   * @param msg The string message (or a key in the message catalog)
   * @param param1 Parameter to the log message.
   * @deprecated Use {@link #logrb(java.util.logging.Level, java.lang.String, java.lang.String,
   * java.util.ResourceBundle, java.lang.String, java.lang.Object...)} instead
   */
  @Deprecated
  public void logrb(Level level, String sourceClass, String sourceMethod,
      String bundleName, String msg, Object param1) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msg);
    lr.setSourceClassName(sourceClass);
    lr.setSourceMethodName(sourceMethod);
    Object params[] = {param1};
    lr.setParameters(params);
    doLog(lr, bundleName);
  }

  /**
   * Log a message, specifying source class, method, and resource bundle name,
   * with an array of object arguments.
   * <p>
   * If the logger is currently enabled for the given message
   * level then a corresponding LogRecord is created and forwarded
   * to all the registered output Handler objects.
   * <p>
   * The msg string is localized using the named resource bundle.  If the
   * resource bundle name is null, or an empty String or invalid
   * then the msg string is not localized.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param sourceClass name of class that issued the logging request
   * @param sourceMethod name of method that issued the logging request
   * @param bundleName name of resource bundle to localize msg, can be null.
   * @param msg The string message (or a key in the message catalog)
   * @param params Array of parameters to the message
   * @deprecated Use {@link #logrb(java.util.logging.Level, java.lang.String, java.lang.String,
   * java.util.ResourceBundle, java.lang.String, java.lang.Object...)} instead.
   */
  @Deprecated
  public void logrb(Level level, String sourceClass, String sourceMethod,
      String bundleName, String msg, Object params[]) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msg);
    lr.setSourceClassName(sourceClass);
    lr.setSourceMethodName(sourceMethod);
    lr.setParameters(params);
    doLog(lr, bundleName);
  }

  /**
   * Log a message, specifying source class, method, and resource bundle,
   * with an optional list of message parameters.
   * <p>
   * If the logger is currently enabled for the given message
   * level then a corresponding LogRecord is created and forwarded
   * to all the registered output Handler objects.
   * <p>
   * The {@code msg} string is localized using the given resource bundle.
   * If the resource bundle is {@code null}, then the {@code msg} string is not
   * localized.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param sourceClass Name of the class that issued the logging request
   * @param sourceMethod Name of the method that issued the logging request
   * @param bundle Resource bundle to localize {@code msg}, can be {@code null}.
   * @param msg The string message (or a key in the message catalog)
   * @param params Parameters to the message (optional, may be none).
   * @since 1.8
   */
  public void logrb(Level level, String sourceClass, String sourceMethod,
      ResourceBundle bundle, String msg, Object... params) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msg);
    lr.setSourceClassName(sourceClass);
    lr.setSourceMethodName(sourceMethod);
    if (params != null && params.length != 0) {
      lr.setParameters(params);
    }
    doLog(lr, bundle);
  }

  /**
   * Log a message, specifying source class, method, and resource bundle name,
   * with associated Throwable information.
   * <p>
   * If the logger is currently enabled for the given message
   * level then the given arguments are stored in a LogRecord
   * which is forwarded to all registered output handlers.
   * <p>
   * The msg string is localized using the named resource bundle.  If the
   * resource bundle name is null, or an empty String or invalid
   * then the msg string is not localized.
   * <p>
   * Note that the thrown argument is stored in the LogRecord thrown
   * property, rather than the LogRecord parameters property.  Thus it is
   * processed specially by output Formatters and is not treated
   * as a formatting parameter to the LogRecord message property.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param sourceClass name of class that issued the logging request
   * @param sourceMethod name of method that issued the logging request
   * @param bundleName name of resource bundle to localize msg, can be null
   * @param msg The string message (or a key in the message catalog)
   * @param thrown Throwable associated with log message.
   * @deprecated Use {@link #logrb(java.util.logging.Level, java.lang.String, java.lang.String,
   * java.util.ResourceBundle, java.lang.String, java.lang.Throwable)} instead.
   */
  @Deprecated
  public void logrb(Level level, String sourceClass, String sourceMethod,
      String bundleName, String msg, Throwable thrown) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msg);
    lr.setSourceClassName(sourceClass);
    lr.setSourceMethodName(sourceMethod);
    lr.setThrown(thrown);
    doLog(lr, bundleName);
  }

  /**
   * Log a message, specifying source class, method, and resource bundle,
   * with associated Throwable information.
   * <p>
   * If the logger is currently enabled for the given message
   * level then the given arguments are stored in a LogRecord
   * which is forwarded to all registered output handlers.
   * <p>
   * The {@code msg} string is localized using the given resource bundle.
   * If the resource bundle is {@code null}, then the {@code msg} string is not
   * localized.
   * <p>
   * Note that the thrown argument is stored in the LogRecord thrown
   * property, rather than the LogRecord parameters property.  Thus it is
   * processed specially by output Formatters and is not treated
   * as a formatting parameter to the LogRecord message property.
   * <p>
   *
   * @param level One of the message level identifiers, e.g., SEVERE
   * @param sourceClass Name of the class that issued the logging request
   * @param sourceMethod Name of the method that issued the logging request
   * @param bundle Resource bundle to localize {@code msg}, can be {@code null}
   * @param msg The string message (or a key in the message catalog)
   * @param thrown Throwable associated with the log message.
   * @since 1.8
   */
  public void logrb(Level level, String sourceClass, String sourceMethod,
      ResourceBundle bundle, String msg, Throwable thrown) {
    if (!isLoggable(level)) {
      return;
    }
    LogRecord lr = new LogRecord(level, msg);
    lr.setSourceClassName(sourceClass);
    lr.setSourceMethodName(sourceMethod);
    lr.setThrown(thrown);
    doLog(lr, bundle);
  }

  //======================================================================
  // Start of convenience methods for logging method entries and returns.
  //======================================================================

  /**
   * Log a method entry.
   * <p>
   * This is a convenience method that can be used to log entry
   * to a method.  A LogRecord with message "ENTRY", log level
   * FINER, and the given sourceMethod and sourceClass is logged.
   * <p>
   *
   * @param sourceClass name of class that issued the logging request
   * @param sourceMethod name of method that is being entered
   */
  public void entering(String sourceClass, String sourceMethod) {
    logp(Level.FINER, sourceClass, sourceMethod, "ENTRY");
  }

  /**
   * Log a method entry, with one parameter.
   * <p>
   * This is a convenience method that can be used to log entry
   * to a method.  A LogRecord with message "ENTRY {0}", log level
   * FINER, and the given sourceMethod, sourceClass, and parameter
   * is logged.
   * <p>
   *
   * @param sourceClass name of class that issued the logging request
   * @param sourceMethod name of method that is being entered
   * @param param1 parameter to the method being entered
   */
  public void entering(String sourceClass, String sourceMethod, Object param1) {
    logp(Level.FINER, sourceClass, sourceMethod, "ENTRY {0}", param1);
  }

  /**
   * Log a method entry, with an array of parameters.
   * <p>
   * This is a convenience method that can be used to log entry
   * to a method.  A LogRecord with message "ENTRY" (followed by a
   * format {N} indicator for each entry in the parameter array),
   * log level FINER, and the given sourceMethod, sourceClass, and
   * parameters is logged.
   * <p>
   *
   * @param sourceClass name of class that issued the logging request
   * @param sourceMethod name of method that is being entered
   * @param params array of parameters to the method being entered
   */
  public void entering(String sourceClass, String sourceMethod, Object params[]) {
    String msg = "ENTRY";
    if (params == null) {
      logp(Level.FINER, sourceClass, sourceMethod, msg);
      return;
    }
    if (!isLoggable(Level.FINER)) {
      return;
    }
    for (int i = 0; i < params.length; i++) {
      msg = msg + " {" + i + "}";
    }
    logp(Level.FINER, sourceClass, sourceMethod, msg, params);
  }

  /**
   * Log a method return.
   * <p>
   * This is a convenience method that can be used to log returning
   * from a method.  A LogRecord with message "RETURN", log level
   * FINER, and the given sourceMethod and sourceClass is logged.
   * <p>
   *
   * @param sourceClass name of class that issued the logging request
   * @param sourceMethod name of the method
   */
  public void exiting(String sourceClass, String sourceMethod) {
    logp(Level.FINER, sourceClass, sourceMethod, "RETURN");
  }


  /**
   * Log a method return, with result object.
   * <p>
   * This is a convenience method that can be used to log returning
   * from a method.  A LogRecord with message "RETURN {0}", log level
   * FINER, and the gives sourceMethod, sourceClass, and result
   * object is logged.
   * <p>
   *
   * @param sourceClass name of class that issued the logging request
   * @param sourceMethod name of the method
   * @param result Object that is being returned
   */
  public void exiting(String sourceClass, String sourceMethod, Object result) {
    logp(Level.FINER, sourceClass, sourceMethod, "RETURN {0}", result);
  }

  /**
   * Log throwing an exception.
   * <p>
   * This is a convenience method to log that a method is
   * terminating by throwing an exception.  The logging is done
   * using the FINER level.
   * <p>
   * If the logger is currently enabled for the given message
   * level then the given arguments are stored in a LogRecord
   * which is forwarded to all registered output handlers.  The
   * LogRecord's message is set to "THROW".
   * <p>
   * Note that the thrown argument is stored in the LogRecord thrown
   * property, rather than the LogRecord parameters property.  Thus it is
   * processed specially by output Formatters and is not treated
   * as a formatting parameter to the LogRecord message property.
   * <p>
   *
   * @param sourceClass name of class that issued the logging request
   * @param sourceMethod name of the method.
   * @param thrown The Throwable that is being thrown.
   */
  public void throwing(String sourceClass, String sourceMethod, Throwable thrown) {
    if (!isLoggable(Level.FINER)) {
      return;
    }
    LogRecord lr = new LogRecord(Level.FINER, "THROW");
    lr.setSourceClassName(sourceClass);
    lr.setSourceMethodName(sourceMethod);
    lr.setThrown(thrown);
    doLog(lr);
  }

  //=======================================================================
  // Start of simple convenience methods using level names as method names
  //=======================================================================

  /**
   * Log a SEVERE message.
   * <p>
   * If the logger is currently enabled for the SEVERE message
   * level then the given message is forwarded to all the
   * registered output Handler objects.
   * <p>
   *
   * @param msg The string message (or a key in the message catalog)
   */
  public void severe(String msg) {
    log(Level.SEVERE, msg);
  }

  /**
   * Log a WARNING message.
   * <p>
   * If the logger is currently enabled for the WARNING message
   * level then the given message is forwarded to all the
   * registered output Handler objects.
   * <p>
   *
   * @param msg The string message (or a key in the message catalog)
   */
  public void warning(String msg) {
    log(Level.WARNING, msg);
  }

  /**
   * Log an INFO message.
   * <p>
   * If the logger is currently enabled for the INFO message
   * level then the given message is forwarded to all the
   * registered output Handler objects.
   * <p>
   *
   * @param msg The string message (or a key in the message catalog)
   */
  public void info(String msg) {
    log(Level.INFO, msg);
  }

  /**
   * Log a CONFIG message.
   * <p>
   * If the logger is currently enabled for the CONFIG message
   * level then the given message is forwarded to all the
   * registered output Handler objects.
   * <p>
   *
   * @param msg The string message (or a key in the message catalog)
   */
  public void config(String msg) {
    log(Level.CONFIG, msg);
  }

  /**
   * Log a FINE message.
   * <p>
   * If the logger is currently enabled for the FINE message
   * level then the given message is forwarded to all the
   * registered output Handler objects.
   * <p>
   *
   * @param msg The string message (or a key in the message catalog)
   */
  public void fine(String msg) {
    log(Level.FINE, msg);
  }

  /**
   * Log a FINER message.
   * <p>
   * If the logger is currently enabled for the FINER message
   * level then the given message is forwarded to all the
   * registered output Handler objects.
   * <p>
   *
   * @param msg The string message (or a key in the message catalog)
   */
  public void finer(String msg) {
    log(Level.FINER, msg);
  }

  /**
   * Log a FINEST message.
   * <p>
   * If the logger is currently enabled for the FINEST message
   * level then the given message is forwarded to all the
   * registered output Handler objects.
   * <p>
   *
   * @param msg The string message (or a key in the message catalog)
   */
  public void finest(String msg) {
    log(Level.FINEST, msg);
  }

  //=======================================================================
  // Start of simple convenience methods using level names as method names
  // and use Supplier<String>
  //=======================================================================

  /**
   * Log a SEVERE message, which is only to be constructed if the logging
   * level is such that the message will actually be logged.
   * <p>
   * If the logger is currently enabled for the SEVERE message
   * level then the message is constructed by invoking the provided
   * supplier function and forwarded to all the registered output
   * Handler objects.
   * <p>
   *
   * @param msgSupplier A function, which when called, produces the desired log message
   * @since 1.8
   */
  public void severe(Supplier<String> msgSupplier) {
    log(Level.SEVERE, msgSupplier);
  }

  /**
   * Log a WARNING message, which is only to be constructed if the logging
   * level is such that the message will actually be logged.
   * <p>
   * If the logger is currently enabled for the WARNING message
   * level then the message is constructed by invoking the provided
   * supplier function and forwarded to all the registered output
   * Handler objects.
   * <p>
   *
   * @param msgSupplier A function, which when called, produces the desired log message
   * @since 1.8
   */
  public void warning(Supplier<String> msgSupplier) {
    log(Level.WARNING, msgSupplier);
  }

  /**
   * Log a INFO message, which is only to be constructed if the logging
   * level is such that the message will actually be logged.
   * <p>
   * If the logger is currently enabled for the INFO message
   * level then the message is constructed by invoking the provided
   * supplier function and forwarded to all the registered output
   * Handler objects.
   * <p>
   *
   * @param msgSupplier A function, which when called, produces the desired log message
   * @since 1.8
   */
  public void info(Supplier<String> msgSupplier) {
    log(Level.INFO, msgSupplier);
  }

  /**
   * Log a CONFIG message, which is only to be constructed if the logging
   * level is such that the message will actually be logged.
   * <p>
   * If the logger is currently enabled for the CONFIG message
   * level then the message is constructed by invoking the provided
   * supplier function and forwarded to all the registered output
   * Handler objects.
   * <p>
   *
   * @param msgSupplier A function, which when called, produces the desired log message
   * @since 1.8
   */
  public void config(Supplier<String> msgSupplier) {
    log(Level.CONFIG, msgSupplier);
  }

  /**
   * Log a FINE message, which is only to be constructed if the logging
   * level is such that the message will actually be logged.
   * <p>
   * If the logger is currently enabled for the FINE message
   * level then the message is constructed by invoking the provided
   * supplier function and forwarded to all the registered output
   * Handler objects.
   * <p>
   *
   * @param msgSupplier A function, which when called, produces the desired log message
   * @since 1.8
   */
  public void fine(Supplier<String> msgSupplier) {
    log(Level.FINE, msgSupplier);
  }

  /**
   * Log a FINER message, which is only to be constructed if the logging
   * level is such that the message will actually be logged.
   * <p>
   * If the logger is currently enabled for the FINER message
   * level then the message is constructed by invoking the provided
   * supplier function and forwarded to all the registered output
   * Handler objects.
   * <p>
   *
   * @param msgSupplier A function, which when called, produces the desired log message
   * @since 1.8
   */
  public void finer(Supplier<String> msgSupplier) {
    log(Level.FINER, msgSupplier);
  }

  /**
   * Log a FINEST message, which is only to be constructed if the logging
   * level is such that the message will actually be logged.
   * <p>
   * If the logger is currently enabled for the FINEST message
   * level then the message is constructed by invoking the provided
   * supplier function and forwarded to all the registered output
   * Handler objects.
   * <p>
   *
   * @param msgSupplier A function, which when called, produces the desired log message
   * @since 1.8
   */
  public void finest(Supplier<String> msgSupplier) {
    log(Level.FINEST, msgSupplier);
  }

  //================================================================
  // End of convenience methods
  //================================================================

  /**
   * Set the log level specifying which message levels will be
   * logged by this logger.  Message levels lower than this
   * value will be discarded.  The level value Level.OFF
   * can be used to turn off logging.
   * <p>
   * If the new level is null, it means that this node should
   * inherit its level from its nearest ancestor with a specific
   * (non-null) level value.
   *
   * @param newLevel the new value for the log level (may be null)
   * @throws SecurityException if a security manager exists, this logger is not anonymous, and the
   * caller does not have LoggingPermission("control").
   */
  public void setLevel(Level newLevel) throws SecurityException {
    checkPermission();
    synchronized (treeLock) {
      levelObject = newLevel;
      updateEffectiveLevel();
    }
  }

  final boolean isLevelInitialized() {
    return levelObject != null;
  }

  /**
   * Get the log Level that has been specified for this Logger.
   * The result may be null, which means that this logger's
   * effective level will be inherited from its parent.
   *
   * @return this Logger's level
   */
  public Level getLevel() {
    return levelObject;
  }

  /**
   * Check if a message of the given level would actually be logged
   * by this logger.  This check is based on the Loggers effective level,
   * which may be inherited from its parent.
   *
   * @param level a message logging level
   * @return true if the given message level is currently being logged.
   */
  public boolean isLoggable(Level level) {
    if (level.intValue() < levelValue || levelValue == offValue) {
      return false;
    }
    return true;
  }

  /**
   * Get the name for this logger.
   *
   * @return logger name.  Will be null for anonymous Loggers.
   */
  public String getName() {
    return name;
  }

  /**
   * Add a log Handler to receive logging messages.
   * <p>
   * By default, Loggers also send their output to their parent logger.
   * Typically the root Logger is configured with a set of Handlers
   * that essentially act as default handlers for all loggers.
   *
   * @param handler a logging Handler
   * @throws SecurityException if a security manager exists, this logger is not anonymous, and the
   * caller does not have LoggingPermission("control").
   */
  public void addHandler(Handler handler) throws SecurityException {
    // Check for null handler
    handler.getClass();
    checkPermission();
    handlers.add(handler);
  }

  /**
   * Remove a log Handler.
   * <P>
   * Returns silently if the given Handler is not found or is null
   *
   * @param handler a logging Handler
   * @throws SecurityException if a security manager exists, this logger is not anonymous, and the
   * caller does not have LoggingPermission("control").
   */
  public void removeHandler(Handler handler) throws SecurityException {
    checkPermission();
    if (handler == null) {
      return;
    }
    handlers.remove(handler);
  }

  /**
   * Get the Handlers associated with this logger.
   * <p>
   *
   * @return an array of all registered Handlers
   */
  public Handler[] getHandlers() {
    return accessCheckedHandlers();
  }

  // This method should ideally be marked final - but unfortunately
  // it needs to be overridden by LogManager.RootLogger
  Handler[] accessCheckedHandlers() {
    return handlers.toArray(emptyHandlers);
  }

  /**
   * Specify whether or not this logger should send its output
   * to its parent Logger.  This means that any LogRecords will
   * also be written to the parent's Handlers, and potentially
   * to its parent, recursively up the namespace.
   *
   * @param useParentHandlers true if output is to be sent to the logger's parent.
   * @throws SecurityException if a security manager exists, this logger is not anonymous, and the
   * caller does not have LoggingPermission("control").
   */
  public void setUseParentHandlers(boolean useParentHandlers) {
    checkPermission();
    this.useParentHandlers = useParentHandlers;
  }

  /**
   * Discover whether or not this logger is sending its output
   * to its parent logger.
   *
   * @return true if output is to be sent to the logger's parent
   */
  public boolean getUseParentHandlers() {
    return useParentHandlers;
  }

  private static ResourceBundle findSystemResourceBundle(final Locale locale) {
    // the resource bundle is in a restricted package
    return AccessController.doPrivileged(new PrivilegedAction<ResourceBundle>() {
      @Override
      public ResourceBundle run() {
        try {
          return ResourceBundle.getBundle(SYSTEM_LOGGER_RB_NAME,
              locale,
              ClassLoader.getSystemClassLoader());
        } catch (MissingResourceException e) {
          throw new InternalError(e.toString());
        }
      }
    });
  }

  /**
   * Private utility method to map a resource bundle name to an
   * actual resource bundle, using a simple one-entry cache.
   * Returns null for a null name.
   * May also return null if we can't find the resource bundle and
   * there is no suitable previous cached value.
   *
   * @param name the ResourceBundle to locate
   * @param userCallersClassLoader if true search using the caller's ClassLoader
   * @return ResourceBundle specified by name or null if not found
   */
  private synchronized ResourceBundle findResourceBundle(String name,
      boolean useCallersClassLoader) {
    // For all lookups, we first check the thread context class loader
    // if it is set.  If not, we use the system classloader.  If we
    // still haven't found it we use the callersClassLoaderRef if it
    // is set and useCallersClassLoader is true.  We set
    // callersClassLoaderRef initially upon creating the logger with a
    // non-null resource bundle name.

    // Return a null bundle for a null name.
    if (name == null) {
      return null;
    }

    Locale currentLocale = Locale.getDefault();
    final LoggerBundle lb = loggerBundle;

    // Normally we should hit on our simple one entry cache.
    if (lb.userBundle != null &&
        name.equals(lb.resourceBundleName)) {
      return lb.userBundle;
    } else if (catalog != null && currentLocale.equals(catalogLocale)
        && name.equals(catalogName)) {
      return catalog;
    }

    if (name.equals(SYSTEM_LOGGER_RB_NAME)) {
      catalog = findSystemResourceBundle(currentLocale);
      catalogName = name;
      catalogLocale = currentLocale;
      return catalog;
    }

    // Use the thread's context ClassLoader.  If there isn't one, use the
    // {@linkplain java.lang.ClassLoader#getSystemClassLoader() system ClassLoader}.
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    if (cl == null) {
      cl = ClassLoader.getSystemClassLoader();
    }
    try {
      catalog = ResourceBundle.getBundle(name, currentLocale, cl);
      catalogName = name;
      catalogLocale = currentLocale;
      return catalog;
    } catch (MissingResourceException ex) {
      // We can't find the ResourceBundle in the default
      // ClassLoader.  Drop through.
    }

    if (useCallersClassLoader) {
      // Try with the caller's ClassLoader
      ClassLoader callersClassLoader = getCallersClassLoader();

      if (callersClassLoader == null || callersClassLoader == cl) {
        return null;
      }

      try {
        catalog = ResourceBundle.getBundle(name, currentLocale,
            callersClassLoader);
        catalogName = name;
        catalogLocale = currentLocale;
        return catalog;
      } catch (MissingResourceException ex) {
        return null; // no luck
      }
    } else {
      return null;
    }
  }

  // Private utility method to initialize our one entry
  // resource bundle name cache and the callers ClassLoader
  // Note: for consistency reasons, we are careful to check
  // that a suitable ResourceBundle exists before setting the
  // resourceBundleName field.
  // Synchronized to prevent races in setting the fields.
  private synchronized void setupResourceInfo(String name,
      Class<?> callersClass) {
    final LoggerBundle lb = loggerBundle;
    if (lb.resourceBundleName != null) {
      // this Logger already has a ResourceBundle

      if (lb.resourceBundleName.equals(name)) {
        // the names match so there is nothing more to do
        return;
      }

      // cannot change ResourceBundles once they are set
      throw new IllegalArgumentException(
          lb.resourceBundleName + " != " + name);
    }

    if (name == null) {
      return;
    }

    setCallersClassLoaderRef(callersClass);
    if (isSystemLogger && getCallersClassLoader() != null) {
      checkPermission();
    }
    if (findResourceBundle(name, true) == null) {
      // We've failed to find an expected ResourceBundle.
      // unset the caller's ClassLoader since we were unable to find the
      // the bundle using it
      this.callersClassLoaderRef = null;
      throw new MissingResourceException("Can't find " + name + " bundle",
          name, "");
    }

    // if lb.userBundle is not null we won't reach this line.
    assert lb.userBundle == null;
    loggerBundle = LoggerBundle.get(name, null);
  }

  /**
   * Sets a resource bundle on this logger.
   * All messages will be logged using the given resource bundle for its
   * specific {@linkplain ResourceBundle#getLocale locale}.
   *
   * @param bundle The resource bundle that this logger shall use.
   * @throws NullPointerException if the given bundle is {@code null}.
   * @throws IllegalArgumentException if the given bundle doesn't have a {@linkplain
   * ResourceBundle#getBaseBundleName base name}, or if this logger already has a resource bundle
   * set but the given bundle has a different base name.
   * @throws SecurityException if a security manager exists, this logger is not anonymous, and the
   * caller does not have LoggingPermission("control").
   * @since 1.8
   */
  public void setResourceBundle(ResourceBundle bundle) {
    checkPermission();

    // Will throw NPE if bundle is null.
    final String baseName = bundle.getBaseBundleName();

    // bundle must have a name
    if (baseName == null || baseName.isEmpty()) {
      throw new IllegalArgumentException("resource bundle must have a name");
    }

    synchronized (this) {
      LoggerBundle lb = loggerBundle;
      final boolean canReplaceResourceBundle = lb.resourceBundleName == null
          || lb.resourceBundleName.equals(baseName);

      if (!canReplaceResourceBundle) {
        throw new IllegalArgumentException("can't replace resource bundle");
      }

      loggerBundle = LoggerBundle.get(baseName, bundle);
    }
  }

  /**
   * Return the parent for this Logger.
   * <p>
   * This method returns the nearest extant parent in the namespace.
   * Thus if a Logger is called "a.b.c.d", and a Logger called "a.b"
   * has been created but no logger "a.b.c" exists, then a call of
   * getParent on the Logger "a.b.c.d" will return the Logger "a.b".
   * <p>
   * The result will be null if it is called on the root Logger
   * in the namespace.
   *
   * @return nearest existing parent Logger
   */
  public Logger getParent() {
    // Note: this used to be synchronized on treeLock.  However, this only
    // provided memory semantics, as there was no guarantee that the caller
    // would synchronize on treeLock (in fact, there is no way for external
    // callers to so synchronize).  Therefore, we have made parent volatile
    // instead.
    return parent;
  }

  /**
   * Set the parent for this Logger.  This method is used by
   * the LogManager to update a Logger when the namespace changes.
   * <p>
   * It should not be called from application code.
   * <p>
   *
   * @param parent the new parent logger
   * @throws SecurityException if a security manager exists and if the caller does not have
   * LoggingPermission("control").
   */
  public void setParent(Logger parent) {
    if (parent == null) {
      throw new NullPointerException();
    }

    // check permission for all loggers, including anonymous loggers
    if (manager == null) {
      manager = LogManager.getLogManager();
    }
    manager.checkPermission();

    doSetParent(parent);
  }

  // Private method to do the work for parenting a child
  // Logger onto a parent logger.
  private void doSetParent(Logger newParent) {

    // System.err.println("doSetParent \"" + getName() + "\" \""
    //                              + newParent.getName() + "\"");

    synchronized (treeLock) {

      // Remove ourself from any previous parent.
      LogManager.LoggerWeakRef ref = null;
      if (parent != null) {
        // assert parent.kids != null;
        for (Iterator<LogManager.LoggerWeakRef> iter = parent.kids.iterator(); iter.hasNext(); ) {
          ref = iter.next();
          Logger kid = ref.get();
          if (kid == this) {
            // ref is used down below to complete the reparenting
            iter.remove();
            break;
          } else {
            ref = null;
          }
        }
        // We have now removed ourself from our parents' kids.
      }

      // Set our new parent.
      parent = newParent;
      if (parent.kids == null) {
        parent.kids = new ArrayList<>(2);
      }
      if (ref == null) {
        // we didn't have a previous parent
        ref = manager.new LoggerWeakRef(this);
      }
      ref.setParentRef(new WeakReference<>(parent));
      parent.kids.add(ref);

      // As a result of the reparenting, the effective level
      // may have changed for us and our children.
      updateEffectiveLevel();

    }
  }

  // Package-level method.
  // Remove the weak reference for the specified child Logger from the
  // kid list. We should only be called from LoggerWeakRef.dispose().
  final void removeChildLogger(LogManager.LoggerWeakRef child) {
    synchronized (treeLock) {
      for (Iterator<LogManager.LoggerWeakRef> iter = kids.iterator(); iter.hasNext(); ) {
        LogManager.LoggerWeakRef ref = iter.next();
        if (ref == child) {
          iter.remove();
          return;
        }
      }
    }
  }

  // Recalculate the effective level for this node and
  // recursively for our children.

  private void updateEffectiveLevel() {
    // assert Thread.holdsLock(treeLock);

    // Figure out our current effective level.
    int newLevelValue;
    if (levelObject != null) {
      newLevelValue = levelObject.intValue();
    } else {
      if (parent != null) {
        newLevelValue = parent.levelValue;
      } else {
        // This may happen during initialization.
        newLevelValue = Level.INFO.intValue();
      }
    }

    // If our effective value hasn't changed, we're done.
    if (levelValue == newLevelValue) {
      return;
    }

    levelValue = newLevelValue;

    // System.err.println("effective level: \"" + getName() + "\" := " + level);

    // Recursively update the level on each of our kids.
    if (kids != null) {
      for (int i = 0; i < kids.size(); i++) {
        LogManager.LoggerWeakRef ref = kids.get(i);
        Logger kid = ref.get();
        if (kid != null) {
          kid.updateEffectiveLevel();
        }
      }
    }
  }


  // Private method to get the potentially inherited
  // resource bundle and resource bundle name for this Logger.
  // This method never returns null.
  private LoggerBundle getEffectiveLoggerBundle() {
    final LoggerBundle lb = loggerBundle;
    if (lb.isSystemBundle()) {
      return SYSTEM_BUNDLE;
    }

    // first take care of this logger
    final ResourceBundle b = getResourceBundle();
    if (b != null && b == lb.userBundle) {
      return lb;
    } else if (b != null) {
      // either lb.userBundle is null or getResourceBundle() is
      // overriden
      final String rbName = getResourceBundleName();
      return LoggerBundle.get(rbName, b);
    }

    // no resource bundle was specified on this logger, look up the
    // parent stack.
    Logger target = this.parent;
    while (target != null) {
      final LoggerBundle trb = target.loggerBundle;
      if (trb.isSystemBundle()) {
        return SYSTEM_BUNDLE;
      }
      if (trb.userBundle != null) {
        return trb;
      }
      final String rbName = isSystemLogger
          // ancestor of a system logger is expected to be a system logger.
          // ignore resource bundle name if it's not.
          ? (target.isSystemLogger ? trb.resourceBundleName : null)
          : target.getResourceBundleName();
      if (rbName != null) {
        return LoggerBundle.get(rbName,
            findResourceBundle(rbName, true));
      }
      target = isSystemLogger ? target.parent : target.getParent();
    }
    return NO_RESOURCE_BUNDLE;
  }

}
